/* The ability class is meant as a superclass for every ability implementation to extend.
 * Unfortunatly Unityscript does not support the abstract keyword, note that this class is not meant to be
 * attached to any gameObject, but instead a component should extend this class.
 *
 * The Ability class provides the basic behaviours every ability should be able to do.
 * It provides functionality for returning an array of colliders for every viable target of the ability, determined by
 * its range, angle variables and if it should be hitting mobs, players or both.
 * Note that abilities are unable to hit through walls.
 *
 * During the line of sight check it ignores layers 8,9 and 10, which should be set for enemies, terrain and the player, respectively
 * 
 * On use one will generally want to call the ApplyAttackModifier() function, which checks if the user has an attack stat,
 * and if so, increases the damage amount by the amount of attack.
 *
 * Author : Reinier
 */
 
class Ability extends MonoBehaviour{

public var abilityName : String = "Basic Ability";	/* The name for the ability */
public var angle : int = 45; 						/* The angle of the field of fire, measures in degrees */
public var cooldown : float = 5;					/* The amount of seconds after which the ability can be used again */
public var currentCooldown : float;					/* The current cooldown, measured in seconds */
public var damage : int = 10;						/* The damage the ability does */
public var hitsMobs : boolean;						/* A boolean if the ability hits enemies or not */
public var hitsPlayers : boolean;					/* A boolean if the ability hits players or not */
public var inputString : String = "Fire1";		/* The input key code, for non-player abilities this should be null */
public var range : float = 10;						/* The ability's range */

/* Every frame the cooldown should be counting down and the input should be checked */
function Update() {
	/* The cooldown should only be updated if the pending cooldown is higher than 0 */
	if(currentCooldown > 0) {
		/* A multiplication by Time.deltaTime is done in order to make the cooldown count down framerate-independent */
		currentCooldown = currentCooldown - 1 * Time.deltaTime;
	}
	/* The input will only be checked if the ability is not on cooldown */
	else {
		if(inputString) {
			/* The input is checked on releasing the key, in order to support any future key combinations */
			if(Input.GetButtonUp(inputString)){
				if(Use()) {
					/* Orders the cooldown to start pending */
					currentCooldown = cooldown;
				}
			}
		}
	}
}

/* The Use() function is meant to be used as the actual usage for the ability, it should return false if the
 * ability is on cooldown, and true if the ability is used. 
 *
 * No virtual keyword is needed since functions are virtual by default in Unityscript 
 */
function Use() : boolean {
}

/* The GetHits function returns all viable targets for the ability in a collider array.
 * The following checks are done:
 * 1) Get all colliders in range of the user
 * 2) An angle check, in order to only return colliders which are in the angle supplied in the editor
 * 3) A tagcheck, in order to only return colliders with either the tag "Enemy" of "Player", depending on the HitsMobs and HitPlayer booleans
 * 4) A line of sight check, in order to only return colliders which are in line of sight of the user
 */
function GetHits() : Collider[] {
	/* The first check, a range check, the Physics.OverlapSphere will find all colliders in range of the user */
	var targetsArr : Collider[];
	targetsArr = Physics.OverlapSphere(transform.position, range);
	
	/* A temporary, resizable array is created to store all viable targets */
	var tempArr = new Array();
	tempArr.length = 0;

	/* The entire array provided by the range check will be filtered to provide all viable targets */
	for(i = 0; i < targetsArr.Length; i++) {
		
		/* The current collider that is being checked */
		var cTarget = targetsArr[i];
		
		/* The second check, an angle check, which checks if the collider is in a viable angle compared to the user of the ability
		 * if the check fails, the collider will not be stored in the temporary array and the loop will check the next collider 
		 */
    	if (Vector3.Angle(cTarget.transform.position - transform.position, transform.forward) < angle) {
    	
    		/* The third check, a tagcheck to see if the collider has the correct tag
    		 * If the check fails, the collider will not be stored in the temporary array and the loop will check the next collider
    		 */
    		if(hitsMobs) {
    			if("Enemy" == cTarget.gameObject.tag) {
    			
    					/* The fourth check, a line of sight check which checks if any objects are between the user and the collider
    					 * If the check fails, the target will not be stored in the temporary array and the loop will check the next collider
    					 */
						if(CheckLoS(cTarget.transform)) {
							/* If all checks have been passed, the collider is a viable target and is put into the temporary array */
    						tempArr.Push(cTarget);
    					}
    			}
    		}
    		
    		if(hitsPlayers) {
    			if("Player" == cTarget.gameObject.tag) {
    					/* The fourth check, a line of sight check which checks if any objects are between the user and the collider
    					 * If the check fails, the target will not be stored in the temporary array and the loop will check the next collider
    					 */
    					if(CheckLoS(cTarget.transform)) {
    						/* If all checks have been passed, the collider is a viable target and is put into the temporary array */
    						tempArr.Push(cTarget);
    					}
    			}
    		}
    		
		}
		
	 }
	
	/* The temporary array is now filled with all the viable targets for the ability
	 * The array is converted to a non-resizable builtin array to provide better performance
	 * when looping through it's contents.
	 */
	var viableTargetsArr : Collider[] = tempArr.ToBuiltin(Collider);	 
	return viableTargetsArr;
}

/* The CheckLoS function throws a raycast to the provided transform and sees if any colliders are between the target and the user of the ability
 * 
 * During this check, the layers 8,9 and 10 are ignored, and are meant to be occupied by enemies, terrain and the player, respectively
 */
function CheckLoS(target : Transform) : boolean {
	/* In order to ignore the layers 8,9 and 10 a bitmask needs to be supplied in the raycast call
	 * To get the right layers, a bitshift will be done
	 * (00000000 00000000 00000000 00000111 to 00000000 00000000 00000111 00000000)
	 */
    var lm = 7 << 8;
    /* The bitshift is inverted to ignore the layers */
    lm = ~lm;
    if(Physics.Raycast(transform.position, target.position - transform.position, Vector3.Distance(target.position, transform.position), lm)) {
    	return false;
    }
    else {
    	return true;
    }
}

/* The function ApplyAttackModifier returns the damage modified by the user's attack stat
 * If the user has no attack stat, the base damage is returned
 */
function ApplyAttackModifier() : int {
		
		var newDamage : int;
		var stats : CharacterStats;
		stats = gameObject.GetComponent(CharacterStats);
		
		if(stats){
			if(stats.attack){
				/* The formula for the attack modifier is : modifier = 1.02^attack */
				var modifier : float = stats.attack.GetAmount();
				modifier = Mathf.Pow(1.02, modifier);
				newDamage = damage * modifier;
				/* return modified damage */
				return newDamage;
			}
			else{
				/* The user has no attack stat, so return base damage */
				return damage;
			}
		}
		else{
			/* The user has no stats at all, so return base damage */
			return damage;
		}
}

}